package crypto

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/sha256"
	"fmt"

	"github.com/tjfoc/gmsm/sm2"
	"github.com/tjfoc/gmsm/x509"
)

type ecdsaKeyGenerator struct {
	curve elliptic.Curve
}

//GetCurve get curve
func (kg *ecdsaKeyGenerator) GetCurve() elliptic.Curve {
	return kg.curve
}

//KeyGen generate key pair
func (kg *ecdsaKeyGenerator) KeyGen() (Key, error) {
	privKey, err := ecdsa.GenerateKey(kg.curve, rand.Reader)
	if err != nil {
		return nil, fmt.Errorf("Failed generating ECDSA key for [%v]: [%s]", kg.curve, err)
	}

	return &ecdsaPrivateKey{privKey}, nil
}

type ecdsaPrivateKey struct {
	privKey *ecdsa.PrivateKey
}

// Bytes converts this key to its byte representation,
// if this operation is allowed.
func (k *ecdsaPrivateKey) Bytes() (raw []byte) {
	priv := k.privKey
	raw = paddedBigBytes(priv.D, priv.Params().BitSize/8)
	return
}

// SKI returns the subject key identifier of this key.
func (k *ecdsaPrivateKey) SKI() []byte {
	if k.privKey == nil {
		return nil
	}

	// Marshall the public key
	raw := elliptic.Marshal(k.privKey.Curve, k.privKey.PublicKey.X, k.privKey.PublicKey.Y)

	// Hash it
	h := sha256.New()
	h.Write(raw)
	return h.Sum(nil)
}

// IsSymmetric returns true if this key is a symmetric key,
// false if this key is asymmetric
func (k *ecdsaPrivateKey) IsSymmetric() bool {
	return false
}

// IsPrivate returns true if this key is a private key,
// false otherwise.
func (k *ecdsaPrivateKey) IsPrivate() bool {
	return true
}

// PublicKey returns the corresponding public key part of an asymmetric public/private key pair.
// This method returns an error in symmetric key schemes.
func (k *ecdsaPrivateKey) PublicKey() PublicKey {
	return &ecdsaPublicKey{&k.privKey.PublicKey}
}

type ecdsaPublicKey struct {
	pubKey *ecdsa.PublicKey
}

// Bytes converts this key to its byte representation,
// if this operation is allowed.
func (k *ecdsaPublicKey) Bytes() (raw []byte) {
	pub := k.pubKey
	if pub == nil || pub.X == nil || pub.Y == nil {
		return nil
	}
	raw = elliptic.Marshal(pub.Curve, pub.X, pub.Y)
	return
}

// SKI returns the subject key identifier of this key.
func (k *ecdsaPublicKey) SKI() []byte {
	if k.pubKey == nil {
		return nil
	}

	// Marshall the public key
	raw := elliptic.Marshal(k.pubKey.Curve, k.pubKey.X, k.pubKey.Y)

	// Hash it
	h := sha256.New()
	h.Write(raw)
	return h.Sum(nil)
}

// IsSymmetric returns true if this key is a symmetric key,
// false if this key is asymmetric
func (k *ecdsaPublicKey) IsSymmetric() bool {
	return false
}

// IsPrivate returns true if this key is a private key,
// false otherwise.
func (k *ecdsaPublicKey) IsPrivate() bool {
	return false
}

//func (k *ecdsaPublicKey) Encrypt(data []byte) ([]byte, error) {
//	return k.pubKey
//}
//
//func (k *ecdsaPrivateKey) Decrypt(data []byte) ([]byte, error) {
//	return k.privKey.Decrypt(data)
//}

//Sign signature
func (k *ecdsaPrivateKey) Sign(data []byte) ([]byte, error) {
	digest, err := Hash(data)
	if err != nil {
		return nil, err
	}
	privateKey := k.privKey
	r, s, err := ecdsa.Sign(rand.Reader, privateKey, digest)
	if err != nil {
		return nil, err
	}

	s, _, err = toLowS(&privateKey.PublicKey, s)
	if err != nil {
		return nil, err
	}

	return marshalECDSASignature(r, s)
}

//GetSignatureAlgorithm get signature algorithm
func (k *ecdsaPrivateKey) GetSignatureAlgorithm() x509.SignatureAlgorithm {
	//switch options.HashFamily {
	//case "SHA256":
	//	return x509.ECDSAWithSHA256
	//case "SHA1":
	//	return x509.ECDSAWithSHA1
	//case "SHA384":
	//	return x509.ECDSAWithSHA384
	//case "SHA512":
	//	return x509.ECDSAWithSHA512
	//}
	switch k.privKey.Curve {
	case sm2.P256Sm2():
		return x509.SM2WithSM3
	default:
		return x509.ECDSAWithSHA256
	}
}

//Verify verify signature
func (k *ecdsaPublicKey) Verify(message []byte, signature []byte) (bool, error) {
	pubKey := k.pubKey
	r, s, err := unmarshalECDSASignature(signature)
	if err != nil {
		return false, fmt.Errorf("Failed unmashalling signature [%s]", err)
	}

	lowS, _, err := toLowS(pubKey, s)
	if err != nil {
		return false, err
	}

	digest, err := Hash(message)
	if err != nil {
		return false, err
	}
	return ecdsa.Verify(pubKey, digest, r, lowS), nil
}
